<html>
  <head>
    <title>ohm/js grammar viz demo</title>
    <meta charset=utf-8>
    <link href="viz.css" rel="stylesheet"></style>
    <script src="../lib.js"></script>
    <script src="../../ohm-js/dist/ohm.js"></script>
    <style>

body {
  position: relative;
}

col1,
col2 {
  display: inline-block;
  position: absolute;
  top: 0;
  bottom: 0;
  overflow: auto;
}

col1 {
  left: 0;
  right: 45%;
}

col2 {
  left: 55%;
  right: 0;
}

#source {
  width: 100%;
  height: 100%;
  font-family: Monaco;
  font-size: 11pt;
  outline: none;
  _border: none;
  border: 0;
  border-right: 2px dashed black;
  margin: 0;
  _margin-right: 5pt;
  resize: none;
}

#source.error {
  border-right: 2px dashed red;
}

#vizDiv {
  margin-left: 5pt;
}

    </style>
  </head>
  <body>
    <col1>
      <textarea id="source" cols="60" autofocus="true" spellcheck="false" wrap="off">
Arithmetic {
  Exp = AddExp

  AddExp
    = AddExp "+" MulExp -- plus
    | AddExp "-" MulExp -- minus
    | MulExp

  MulExp
    = MulExp "*" ExpExp -- times
    | MulExp "/" ExpExp -- divide
    | ExpExp

  ExpExp
    = PriExp "^" ExpExp -- power
    | PriExp

  PriExp
    = "(" Exp ")" -- paren
    | "+" PriExp  -- pos
    | "-" PriExp  -- neg
    | ident
    | number

  ident  (an identifier)
    = letter alnum*

  number  (a number literal)
    = digit* "." digit+  -- fract
    | digit+             -- whole
}</textarea>
    </col1>
    <col2>
      <div id="vizDiv"></div>
    </col2>
    <script>

var curr = document.getElementById('vizDiv');

function enter(tag) {
  var child = makeElement(tag);
  curr.appendChild(child);
  curr = child;
}

function leave() {
  curr = curr.parentElement;
}

function addNode(node) {
  curr.appendChild(node);
}

function add(/* tagName, child1, child2, ... */) {
  addNode(makeElement.apply(this, arguments));
}

var s = ohm.ohmGrammar.createSemantics();

s.addOperation('viz', {
  Grammar: function(n, s, _l, rs, _r) {
    enter('grammar');
      add('name', n.viz());
      s.viz();
      enter('rules');
        rs.viz();
      leave();
    leave();
  },

  SuperGrammar: function(_, n) {
    enter('super');
      add('name', n.viz());
    leave();
  },

  Rule: function(expr) {
    enter('rule');
      expr.viz();
    leave();
  },
  Rule_define: function(n, fs, d, _, b) {
    add('name', n.viz());
    d.viz();
    enter('ruleDefineBody');
      b.viz();
    leave();
  },
  Rule_override: function(n, fs, _, b) {
    add('name', n.viz());
    enter('ruleOverrideBody');
      b.viz();
    leave();
  },
  Rule_extend: function(n, fs, _, b) {
    add('name', n.viz());
    enter('ruleExtendBody');
      b.viz();
    leave();
  },
  RuleBody: function(_, terms) {
    enter('alt');
      terms.vizChoice();
    leave();
  },

  ruleDescr: function(_l, t, _r) {
    add('description', t.viz());
  },
  ruleDescrText: function(_) {
    return this.sourceString;
  },
  Alt: function(xs) {
    enter('alt');
      xs.vizChoice();
    leave();
  },
  TopLevelTerm_inline: function(x, n) {
    x.viz();
    n.viz();
  },
  caseName: function(_d, _s1, n, _s2, _t) {
    add('caseName', n.viz());
  },
  Seq: function(expr) {
    enter('seq');
      expr.viz();
    leave();
  },
  Iter: function(expr) {
    expr.viz();
  },
  Iter_star: function(x, _) {
    enter('star');
      x.viz();
    leave();
  },
  Iter_plus: function(x, _) {
    enter('plus');
      x.viz();
    leave();
  },
  Iter_opt: function(x, _) {
    enter('opt');
      x.viz();
    leave();
  },
  Pred: function(expr) {
    expr.viz();
  },
  Pred_not: function(_, x) {
    enter('not');
      x.viz();
    leave();
  },
  Pred_lookahead: function(_, x) {
    enter('lookahead');
      x.viz();
    leave();
  },
  Base: function(expr) {
    expr.viz();
  },
  Base_application: function(rule, ps) {
    add('app', rule.viz());
  },
  Base_terminal: function(expr) {
    expr.viz();
  },
  Base_paren: function(_l, x, _r) {
    enter('paren');
      x.viz();
    leave();
  },
  ident: function(n) {
    return n.viz();
  },
  name: function(_first, _rest) {
    return this.sourceString;
  },
  terminal: function(_lq, cs, _rq) {
    add('terminal', escape(eval(this.sourceString)));
  }
});

s.addOperation('vizChoice', {
  _nonterminal: function(children) {
    enter('choice');
      this.viz();
    leave();
  },
  NonemptyListOf: function(x, _, xs) {
    enter('choice');
      x.viz();
    leave();
    xs.vizChoice();
  }
});

function escape(str) {
  var node = document.createElement('span');
  for (var idx = 0; idx < str.length; idx++) {
    if (str.charCodeAt(idx) < 32) {
      var c;
      switch (str.charAt(idx)) {
        case '\r':
          c = '\\r';
          break;
        case '\n':
          c = '\\n';
          break;
        case '\t':
          c = '\\t';
          break;
        default:
          c = '(ascii ' + str.charCodeAt(idx) + ')';
      }
      node.appendChild(makeElement('specialChar', c));
    } else {
      node.appendChild(document.createTextNode(str.charAt(idx)));
    }
  }
  return node;
}

var source = document.getElementById('source');
var vizDiv = document.getElementById('vizDiv');

source.oninput = function() {
  var m = ohm.ohmGrammar.match(source.value, 'Grammar');
  if (m.failed()) {
    console.log(m.message);
    source.className = 'error';
  } else {
    removeChildren(vizDiv);
    s(m).viz();
    source.className = undefined;
  }
};

source.oninput();

window.test = function(t) {
  // A very basic test -- check that all case names in the grammar appear as a
  // <casename> element in the DOM.
  var nodes = document.querySelectorAll('casename');
  var caseNames = Array.prototype.map.call(nodes, function(node) {
    return node.textContent;
  });
  t.deepEqual(
      caseNames,
      ['plus', 'minus', 'times', 'divide', 'power', 'paren', 'pos', 'neg', 'fract', 'whole'],
      'the expected case names appear in the DOM');
  t.end();
};

    </script>
  </body>
</html>
